/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu.
 *
 * Copyright (C) 2005-2011  Dirk Beyer
 *
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 *
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */
package org.sosy_lab.ccvisu.writers;

import java.awt.Color;
import java.io.PrintWriter;

import org.sosy_lab.ccvisu.CCVisuController;
import org.sosy_lab.ccvisu.CCVisuOptions;
import org.sosy_lab.ccvisu.InformationProvider;
import org.sosy_lab.ccvisu.graph.GraphData;
import org.sosy_lab.ccvisu.graph.GraphEdge;
import org.sosy_lab.ccvisu.graph.GraphVertex;
import org.sosy_lab.common.configuration.Configuration;
import org.sosy_lab.common.configuration.InvalidConfigurationException;

/**
 * Writer for layouts in SVG format (Scalable Vector Graphs, XML, W3C).
 */
public class WriterDataLayoutSVG extends WriterDataLayout {

  /** used to build unique id for the edges */
  private int                    edgeNumber = 0;

  private final CCVisuController controller;

  private final CCVisuOptions    options;

  private final boolean          showAllLabels;

  /**
   * Constructor.
   * @throws InvalidConfigurationException
   */
  public WriterDataLayoutSVG(PrintWriter out, GraphData graph, CCVisuOptions options,
      CCVisuController controller, Configuration config, boolean showAllLabels)
          throws InvalidConfigurationException {

    super(out, graph, config);
    this.controller = controller;
    this.options = options;
    this.showAllLabels = showAllLabels;
  }

  /**
   * Writes the layout in graphics format SVG.
   */
  @Override
  public void write() {
    int size = (int) (1000 * options.getScalePos());

    // Header
    out.println("<?xml version=\"1.0\" standalone=\"no\"?>");
    out.println();
    out.println("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"");
    out.println("  \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
    out.println();
    out.println("<!-- Generated by " + InformationProvider.toolDescription());
    out.println("     " + InformationProvider.currentDateTime() + " -->");
    out.println();
    out.println("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"");
    out.println("     xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
    out.println("     width=\"100%\" height=\"100%\"");
    out.println("     viewBox=\"0 0 " + size + " " + size + "\">");
    out.println();
    out.println(" <title>Visualization "
        + controller.getInputName()
        + "</title>");
    out.println();
    out.println(" <script><![CDATA[");
    out.println();
    out.println("  /*");
    out.println("   * Returns the innermost SVG object that triggered the event");
    out.println("   * and that has a non-null id attribute. This can be either the event's");
    out.println("   * target itself, or its parent node, or the first ancestor of the target");
    out.println("   * with a non-null id.");
    out.println("   *");
    out.println("   * Input Parameters:");
    out.println("   *   evt - The JavaScript object describing the triggering event.");
    out.println("   * Return Value:");
    out.println("   *   The target node or first target's ancestor with a non-null id.");
    out.println("   */");
    out.println("  function get_target(evt) {");
    out.println("    var target = evt.target;");
    out.println("    while (target && !target.getAttribute('id')) {");
    out.println("      target = target.parentNode;");
    out.println("    }");
    out.println("    return target;");
    out.println("  }");
    out.println();
    out.println("  /*");
    out.println("   * Adds or removes a duplicate of the text object that");
    out.println("   * corresponds to the circle that was clicked on / pointed to:");
    out.println("   * The circle node is the evt.target.");
    out.println("   * If the text node is already there, the node gets removed.");
    out.println("   * Otherwise the text node is cloned. The cloned node's id is changed.");
    out.println("   * The cloned node is appended to the 'contents' group.");
    out.println("   *");
    out.println("   * Input Parameters:");
    out.println("   *   evt -     JavaScript object describing the triggering event.");
    out.println("   *   postfix - String to distinguish between click and move.");
    out.println("   */");
    out.println("  function annot_toggle(evt, postfix) {");
    out.println("    // Retrieve node of object that was clicked on.");
    out.println("    var target = get_target(evt);");
    out.println("    var svgdoc = target.ownerDocument;");
    out.println();
    out.println("    // Retrieve annotation node.");
    out.println("    var annotNode = svgdoc.getElementById(target.getAttribute('id')");
    out.println("                                          + '__text' + postfix);");
    out.println("    // Check whether annotation is already set.");
    out.println("    if (annotNode) {");
    out.println("      // Remove object's node from its group node.");
    out.println("      var groupnode = annotNode.parentNode;");
    out.println("      groupnode.removeChild (annotNode);");
    out.println("    } else {");
    out.println("      // Clone the text object and set its attributes.");
    out.println("      var text = svgdoc.getElementById(target.getAttribute('id') + '__text');");
    out.println("      annotNode = text.cloneNode(true);");
    out.println("      annotNode.setAttribute('id', target.getAttribute('id') + '__text' + postfix);");
    out.println("      // Retrieve the node for 'contents' group.");
    out.println("      var contents = svgdoc.getElementById('contents');");
    out.println("      // Insert the cloned object into the contents group node.");
    out.println("      contents.parentNode.appendChild(annotNode);");
    out.println("      window.status = target.getAttribute('id');");
    out.println("    }");
    out.println("  }");
    out.println();
    out.println(" ]]></script>");
    out.println();

    // Body
    writeGraphicsLayout(graph.getVertices(), graph.getEdges(), size);

    // Footer
    out.println("</svg>");
  }

  /**
   * Writes a vertex in SVG format.
   * @param vertex     The vertex object, to access vertex attributes.
   * @param xPos       x coordinate of the vertex.
   * @param yPos       y coordinate of the vertex.
   * @param zPos       z coordinate of the vertex.
   * @param width      Width of the vertex.
   * @param height     Height of the vertex.
   */
  @Override
  public void writeVertex(GraphVertex vertex, int xPos, int yPos, int zPos,
      int width, int height, Color color) {

    String name = vertex.getName();

    // remove double quote
    if ((name.length() > 0 && name.charAt(0) == '"') && name.endsWith("\"")) {
      name = name.substring(1, name.length() - 1);
    }

    // Prepare color string for vertex.
    // Convert to hex RGB value and get rid of the alpha value.
    String colorHex = Integer.toHexString(color.getRGB() & 0x00FFFFFF);

    // Fill up to 6 digits.
    colorHex = "000000" + colorHex;
    colorHex = colorHex.substring(colorHex.length() - 6, colorHex.length());

    // Prepare color string for annotation text.
    Color textColor = new Color(0xffffffff - options.getBackColor().get().getRGB());

    // Convert to hex RGB value and get rid of the alpha value.
    String textColorHex = Integer.toHexString(textColor.getRGB() & 0x00FFFFFF);

    // Fill up to 6 digits.
    textColorHex = "000000" + textColorHex;
    textColorHex = textColorHex.substring(textColorHex.length() - 6, textColorHex.length());

    // Write the definition for the annotation.
    out.println(" <defs>  <text id=\"" + name + "__text\" "
        + "x=\"" + (xPos + width + 3) + "\" "
        + "y=\"" + (yPos + 3) + "\" "
        + "style=\"font-family: helvetica, sans-serif;font-size:"
        + controller.getFontSize()
        + "pt;fill:#" + textColorHex + "\"> "
        + name + (vertex.hasSelfLoop() ? "-LOOP" : "")
        + " </text> </defs>");

    // Write graphical objects.
    out.println(" <g id=\"contents\">");

    // Write vertex.
    String strokeString = "";
    if (blackCircle) {
      strokeString = " stroke=\"" + ringColor.toString() + "\"";
    }

    //link information
    if (openUrl) {
      out.println("    <a xlink:href=\"" + name + "\">");
    }

    out.println("  <circle id=\"" + name + "\" "
        + "cx=\"" + xPos + "\" "
        + "cy=\"" + yPos + "\" "
        + "r=\"" + width + "\" "
        + "fill=\"#" + colorHex + "\""
        + strokeString
        + " onmouseover=\"annot_toggle(evt, '_move')\""
        + " onmouseout=\"annot_toggle(evt, '_move')\""
        + " onclick=\"annot_toggle(evt, '_click')\"" + " />");

    if (openUrl) {
      out.println("    </a>");
    }

    // Write annotation, only if required.
    if (vertex.isShowName()) {
      out.println("  <text id=\"" + name + "__text_click\" "
          + "x=\"" + (xPos + width + 3) + "\" "
          + "y=\"" + (yPos + 3) + "\" "
          + "style=\"font-family: helvetica, sans-serif;font-size:"
          + controller.getFontSize()
          + "pt;fill:#" + textColorHex + "\"> "
          + name + " </text>");
    }

    out.println(" </g>");
  }

  /**
   * Writes an edge.
   * @param edge        an edge in graph.edges
   * @param xPos1       x coordinate of the first point.
   * @param yPos1       y coordinate of the first point.
   * @param zPos1       z coordinate of the first point.
   * @param xPos2       x coordinate of the second point.
   * @param yPos2       y coordinate of the second point.
   * @param zPos2       z coordinate of the second point.
   */
  @Override
  public void writeEdge(GraphEdge edge, int xPos1, int yPos1, int zPos1,
      int xPos2, int yPos2, int zPos2) {

    String edgeName = edge.getRelationName();

    // Prepare color string for edge and annotation text.
    // Move this to initialization in main perhaps:
    //   Color textColor = new Color(0xffffffff - options.backColor.get().getRGB());
    // Convert to hex RGB value and get rid of the alpha value.
    String colorHex = Integer.toHexString(edge.getColor().getRGB() & 0x00FFFFFF);

    // Fill up to 6 digits.
    colorHex = "000000" + colorHex;
    colorHex = colorHex.substring(colorHex.length() - 6, colorHex.length());

    int fontSize = controller.getFontSize();

    //    Write the definition for the annotation.
    out.println(" <defs> <text id=\"" + edgeName + edgeNumber + "__text\" "
        + "x=\"" + ((xPos1 + xPos2) / 2 + fontSize + 3) + "\" "
        + "y=\"" + ((yPos1 + yPos2) / 2 + fontSize + 3) + "\" "
        + "style=\"font-family: helvetica, sans-serif;"
          + "font-size:" + fontSize + "pt;"
          + "fill:#" + colorHex + "\"> "
        + edgeName + " </text> </defs>");

    // Write graphical objects.
    out.println(" <g id=\"contents\" stroke=\"#" + colorHex + "\">");

    // Draw the line.
    out.println("<line id=\"" + edgeName + edgeNumber + "\" "
        + "style=\"stroke-width:1;fill:none;\" "
        + "y1=\"" + yPos1 + "\" " + "y2=\"" + yPos2 + "\" "
        + "x1=\"" + xPos1 + "\" " + "x2=\"" + xPos2 + "\" "
        + " onmouseover=\"annot_toggle(evt, '_move')\""
        + " onmouseout=\"annot_toggle(evt, '_move')\""
        + " onclick=\"annot_toggle(evt, '_click')\" />");

    // Draw the arrow head.
    int[] arr = paintArrow(xPos1, yPos1, xPos2, yPos2);

    out.println("<line id=\"" + edgeName + edgeNumber + "_tip1" + "\" "
        + "style=\"stroke-width:1;fill:none;\" "
        + "y1=\"" + arr[1] + "\" " + "y2=\"" + arr[3] + "\" "
        + "x1=\"" + arr[0] + "\" " + "x2=\"" + arr[2] + "\" "
        + " onmouseover=\"annot_toggle(evt, '_move')\""
        + " onmouseout=\"annot_toggle(evt, '_move')\""
        + " onclick=\"annot_toggle(evt, '_click')\" />");

    out.println("<line id=\"" + edgeName + edgeNumber + "_tip2" + "\" "
        + "style=\"stroke-width:1;fill:none;\" "
        + "y1=\"" + arr[5] + "\" " + "y2=\"" + arr[7] + "\" "
        + "x1=\"" + arr[4] + "\" " + "x2=\"" + arr[6] + "\" "
        + " onmouseover=\"annot_toggle(evt, '_move')\""
        + " onmouseout=\"annot_toggle(evt, '_move')\""
        + " onclick=\"annot_toggle(evt, '_click')\" />");

    // Write annotation, only if required.
    if (showAllLabels) {
      out.println("  <text id=\"" + edgeName + edgeNumber + "__text\" "
          + "x=\"" + ((xPos1 + xPos2) / 2 + fontSize + 3) + "\" "
          + "y=\"" + ((yPos1 + yPos2) / 2 + fontSize + 3) + "\" "
          + "style=\"font-family: helvetica, sans-serif;"
            + "font-size:" + fontSize + "pt\"> "
          + edgeName
          + " </text> ");
    }

    out.println(" </g>");

    edgeNumber++;
  }
}
